/*
 * Copyright (c) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <ctype.h>
#include <securec.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>

#include "ohos_errno.h"
#include "ohos_types.h"
#include "utils_config.h"
#include "utils_file.h"

#include "iot_store_manager.h"

#if (USER_KV_STORE)
#define INDX_KEY        "//offset.idx"
#define INDX_TMP        "//offset.tmp"
#define DATA_KEY        "//data.info"
#define DATA_TMP        "//data.tmp"

#define TEMP_KEY        "//tmp.idx"

#define STORE_DEBUG
#ifdef  STORE_DEBUG
#define STORE_DBG(fmt, args...)     printf("[%s|%d][STORE_DEBUG]" fmt, __func__, __LINE__, ##args)
#define STORE_ERR(fmt, args...)     printf("[%s|%d][STORE_ERROR]" fmt, __func__, __LINE__, ##args)
#else
#define STORE_DBG(fmt, args...)     do {} while (0)
#define STORE_ERR(fmt, args...)     do {} while (0)
#endif

#define UTILS_CLOSE(fdx, idx)       ({                              \
                                        if ((fdx) != -1) {          \
                                            close((fdx));  \
                                            (fdx) = -1;             \
                                        }                           \
                                        if ((idx) != -1) {          \
                                            close((idx));  \
                                            (idx) = -1;             \
                                        }                           \
                                    })

static int GetFileSize(const char *filename)
{
    int offset = 0;
    struct stat fileStat;
    if (stat(filename, &fileStat) == 0) {
        offset = fileStat.st_size;
    }

    return offset;
}

int StoreManagerAddData(const char *data, int length)
{
    int total = 0;
    int offset = 0;
    int ret = 0;
    int fd = -1;
    int fidx = -1;

    offset = GetFileSize(DATA_KEY);

    if (data == NULL || length <= 0) {
        STORE_ERR("NULL POINT! \n");
        return -1;
    }

    fidx = open(INDX_KEY, O_RDWR | O_CREAT | O_APPEND, 0);
    if (fidx < 0) {
        STORE_ERR("UtilsFileOpen %s failed! \n", INDX_KEY);
        return -1;
    }

    fd = open(DATA_KEY, O_RDWR | O_CREAT | O_APPEND, 0);
    if (fd < 0) {
        ret = -1;
        STORE_ERR("UtilsFileOpen %s failed! \n", DATA_KEY);
        goto ADD_EXIT;
    }

    lseek(fidx, 0, SEEK_SET);

    if (read(fidx, &total, sizeof(int)) < 0) {
        ret = -1;
        STORE_ERR("UtilsFileRead total failed! \n");
        goto ADD_EXIT;
    }
    total += 1;
    printf("save total = %d! \n", total);

    lseek(fidx, 0, SEEK_SET);

    if (write(fidx, &total, sizeof(int)) < 0) {
        ret = -1;
        STORE_ERR("UtilsFileWrite total failed! \n");
        goto ADD_EXIT;
    }
    lseek(fidx, 0, SEEK_END);

    if (write(fidx, &offset, sizeof(int)) < 0) {
        ret = -1;
        STORE_ERR("UtilsFileWrite offset failed! \n");
        goto ADD_EXIT;
    }
    lseek(fd, 0, SEEK_END);
    write(fd, &length, sizeof(int));

    if (write(fd, data, length) < 0) {
        ret = -1;
        STORE_ERR("UtilsFileWrite data failed! \n");
        goto ADD_EXIT;
    }
    STORE_DBG("save data[%d|%s]->%d success! \n", length, data, offset);
ADD_EXIT:

    UTILS_CLOSE(fd, fidx);

    return ret;
}

int StoreManagerUpdateData(int idx, const char *data, int length)
{
    int fd = -1;
    int fidx = -1;
    int tmp = -1;
    int tmpidx = -1;
    int total = 0;

    fidx = open(INDX_KEY, O_RDWR, 0);
    if (fidx < 0) {
        printf("UtilsFileOpen INDX_KEY failed! \n");
        return -1;
    }

    lseek(fidx, 0, SEEK_SET);
    if (read(fidx, (char *)&total, sizeof(int)) < 0) {
        printf("UtilsFileRead failed! \n");
        goto ERR_UPDATE;
    }
    if (total <= 0) {
        printf("total is 0! \n");
        goto ERR_UPDATE;
    }

    if (idx >= total) {
        printf("idx(%d) > total(%d) \n", idx, total);
        goto ERR_UPDATE;
    }

    fd = open(DATA_KEY, O_RDWR, 0);
    if (fd < 0) {
        printf("UtilsFileOpen DATA_KEY failed! \n");
        goto ERR_UPDATE;
    }

    tmp = open(DATA_TMP, O_CREAT | O_RDWR, 0);
    if (tmp < 0) {
        printf("UtilsFileOpen DATA_TMP failed! \n");
        goto ERR_UPDATE;
    }
    tmpidx = open(INDX_TMP, O_CREAT | O_RDWR, 0);
    if (tmpidx < 0) {
        printf("UtilsFileOpen INDX_TMP failed! \n");
        goto ERR_UPDATE;
    }

    if (write(tmpidx, &total, sizeof(int)) < 0) {
        printf("UtilsFileWrite tmpidx failed! \n");
        goto ERR_UPDATE;
    }

    for (int i = 0; i <= total; i++) {
        void *pdata = NULL;
        int offset = 0;
        int psize = 0;

        if (read(fidx, (void *)&offset, sizeof(int)) < 0) {
            printf("UtilsFileRead fidx  offset failed! \n");
            goto ERR_UPDATE;
        }
        if (i == idx) {
            offset = GetFileSize(DATA_TMP);
            write(tmp, data, length);
            write(tmpidx, (void *)&offset, sizeof(int));
            continue;
        }

        lseek(fd, offset, SEEK_SET);
        if (read(fd, &psize, sizeof(int)) < 0) {
            printf("UtilsFileRead fd  psize failed! \n");
            goto ERR_UPDATE;
        }
        if (psize <= 0) {
            goto ERR_UPDATE; 
        }
        pdata = malloc(psize);
        if (read(fd, pdata, psize) < 0) {
            free(pdata);
            goto ERR_UPDATE;
        }
        offset = GetFileSize(DATA_TMP);
        write(tmp, psize, sizeof(int));
        write(tmp, pdata, psize);
        write(tmpidx, &offset, sizeof(int));

        free(pdata);
    }

    UTILS_CLOSE(fd, fidx);
    UTILS_CLOSE(tmp, tmpidx);

    unlink(INDX_KEY);
    unlink(DATA_KEY);

    rename(DATA_TMP, DATA_KEY);
    rename(INDX_TMP, INDX_KEY);

    return 0;
ERR_UPDATE:

    UTILS_CLOSE(fd, fidx);
    UTILS_CLOSE(tmp, tmpidx);

    return -1;
}

int StoreManagerDeleteDataWithId(int idx)
{
    int fd = -1;
    int fidx = -1;
    int tmp = -1;
    int tmpidx = -1;
    int total = 0;

    fidx = open(INDX_KEY, O_RDWR, 0);
    if (fidx < 0) {
        return -1;
    }

    lseek(fidx, 0, SEEK_SET);
    if (read(fidx, &total, sizeof(int)) < 0) {
        goto ERR_DELETE_ID;
    }
    if (total <= 0) {
        goto ERR_DELETE_ID;
    }

    if (idx >= total) {
        goto ERR_DELETE_ID;
    }

    if (total == 1) {
        close(fidx);
        unlink(INDX_KEY);
        unlink(DATA_KEY);
        return 0;
    }

    total -= 1;
    fd = open(DATA_KEY, O_RDWR, 0);
    if (fd < 0) {
        goto ERR_DELETE_ID;
    }

    tmp = open(DATA_TMP, O_CREAT | O_RDWR, 0);
    if (tmp < 0) {
        goto ERR_DELETE_ID;
    }
    tmpidx = open(INDX_TMP, O_CREAT | O_RDWR, 0);
    if (tmpidx < 0) {
        goto ERR_DELETE_ID;
    }

    if (write(tmpidx, &total, sizeof(int)) < 0) {
        goto ERR_DELETE_ID;
    }

    for (int i = 0; i <= total; i++) {
        void *data = NULL;
        int offset = 0;
        int size = 0;

        if (read(fidx, &offset, sizeof(int)) < 0) {
            goto ERR_DELETE_ID;
        }
        if (i == idx) {
            continue;
        }

        lseek(fd, offset, SEEK_SET);
        if (read(fd, &size, sizeof(int)) < 0) {
            goto ERR_DELETE_ID;
        }
        if (size <= 0) {
            goto ERR_DELETE_ID;
        }
        data = malloc(size);
        if (read(fd, data, size) < 0) {
            free(data);
            goto ERR_DELETE_ID;
        }
        offset = GetFileSize(DATA_TMP);
        write(tmp, size, sizeof(int));
        write(tmp, data, size);
        write(tmpidx, &offset, sizeof(int));

        free(data);
    }

    UTILS_CLOSE(fd, fidx);
    UTILS_CLOSE(tmp, tmpidx);

    unlink(INDX_KEY);
    unlink(DATA_KEY);

    rename(DATA_TMP, DATA_KEY);
    rename(INDX_TMP, INDX_KEY);

    return 0;

ERR_DELETE_ID:

    UTILS_CLOSE(fd, fidx);
    UTILS_CLOSE(tmp, tmpidx);

    return -1;
}

int StoreManagerDeleteData(const char *data)
{
    int fd = -1;
    int fidx = -1;
    int tmp = -1;
    int tmpidx = -1;
    int total = 0;

    fidx = open(INDX_KEY, O_RDWR, 0);
    if (fidx < 0) {
        return -1;
    }

    lseek(fidx, 0, SEEK_SET);
    if (read(fidx, &total, sizeof(int)) < 0) {
        goto ERR_DELETE_DATA;
    }
    if (total <= 0) {
        goto ERR_DELETE_DATA;
    }

    fd = open(DATA_KEY, O_RDWR, 0);
    if (fd < 0) {
        goto ERR_DELETE_DATA;
    }

    tmp = open(DATA_TMP, O_CREAT | O_RDWR, 0);
    if (tmp < 0) {
        goto ERR_DELETE_DATA;
    }
    tmpidx = open(INDX_TMP, O_CREAT | O_RDWR, 0);
    if (tmpidx < 0) {
        goto ERR_DELETE_DATA;
    }

    if (write(tmpidx, &total, sizeof(int)) < 0) {
        goto ERR_DELETE_DATA;
    }

    for (int i = 0; i <= total; i++) {
        void *pdata = NULL;
        int offset = 0;
        int psize = 0;

        if (read(fidx, &offset, sizeof(int)) < 0) {
            goto ERR_DELETE_DATA;
        }

        lseek(fd, offset, SEEK_SET);
        if (read(fd, &psize, sizeof(int)) < 0) {
            goto ERR_DELETE_DATA;
        }
        if (psize <= 0) {
            goto ERR_DELETE_DATA;
        }
        pdata = malloc(psize);
        if (read(fd, pdata, psize) < 0) {
            free(pdata);
            goto ERR_DELETE_DATA;
        }

        if (memcmp(pdata, data, psize) != 0) {
            offset = GetFileSize(DATA_TMP);
            write(tmp, psize, sizeof(int));
            write(tmp, pdata, psize);
            write(tmpidx, &offset, sizeof(int));
        }

        free(pdata);
    }

    UTILS_CLOSE(fd, fidx);
    UTILS_CLOSE(tmp, tmpidx);

    unlink(INDX_KEY);
    unlink(DATA_KEY);

    rename(DATA_TMP, DATA_KEY);
    rename(INDX_TMP, INDX_KEY);

    return 0;
ERR_DELETE_DATA:

    UTILS_CLOSE(fd, fidx);
    UTILS_CLOSE(tmp, tmpidx);

    return -1;
}

int StoreManagerGetTotal(void)
{
    int total = 0;
    int fd = open(INDX_KEY, O_RDWR, 0);

    if (fd < 0) {
        STORE_ERR("open INDX_KEY failed! \n");
        return -1;
    }

    lseek(fd, 0, SEEK_SET);

    if (read(fd, &total, sizeof(int)) < 0) {
        STORE_ERR("read total failed! \n");
        total = -1;
    } else {
        STORE_ERR("read total =%d \n", total);
    }

    close(fd);

    return total;
}

int StoreManagerGetData(char *data, int size, int idx)
{
    int fd = -1;
    int fidx = -1;
    unsigned int total = 0;
    unsigned int offset = 0;
    unsigned int psize = 0;

    fidx = open(INDX_KEY, O_RDWR, 0);
    if (fidx < 0) {
        return -1;
    }

    fd = open(DATA_KEY, O_RDWR, 0);
    if (fd < 0) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }
    lseek(fidx, 0, SEEK_SET);

    if (read(fidx, &total, sizeof(int)) < 0) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    if (idx >= total) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    if (lseek(fidx, (idx + 1) * sizeof(int), SEEK_SET) < 0) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    if (read(fidx, &offset, sizeof(int)) < 0) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    if (lseek(fd, offset, SEEK_SET) < 0) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    if (read(fd, &psize, sizeof(int)) < 0) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    if (size < psize) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    if (read(fd, data, psize) < 0) {
        UTILS_CLOSE(fd, fidx);
        return -1;
    }

    UTILS_CLOSE(fd, fidx);

    return 0;
}

void StoreManagerDelete(void)
{
    unlink(DATA_KEY);
    unlink(INDX_KEY);
}

#endif
